package com.xiaomaoguai.hessian.config;

import com.xiaomaoguai.hessian.annotation.EnableHessianClient;
import com.xiaomaoguai.hessian.annotation.HessianClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cglib.core.TypeUtils;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.filter.AbstractClassTestingTypeFilter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.remoting.caucho.HessianProxyFactoryBean;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @fileName: HessianClientRegistrar.java
 * @author: WeiHui
 * @date: 2018/9/19 16:48
 * @version: v1.0.0
 * @since JDK 1.8
 */
@Slf4j
public class HessianClientRegistrar implements EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware, ImportBeanDefinitionRegistrar {

	private Environment environment;

	private ClassLoader classLoader;

	private ResourceLoader resourceLoader;

	@Override
	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}

	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerHessianClients(metadata, registry);
	}

	private void registerHessianClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		HessianClientProperties hessianClientProperties = registerHessianClientProperties(registry);

		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(HessianClient.class);
		AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {

			@Override
			protected boolean match(ClassMetadata metadata) {
				return metadata.isInterface();
			}
		};
		scanner.addIncludeFilter(annotationTypeFilter);
		scanner.addIncludeFilter(filter);

		Set<String> basePackages = getBasePackages(metadata);
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(), "@HessianClient can only be specified on an interface");
					registerHessianClient(registry, candidateComponent, hessianClientProperties);
				}
			}
		}
	}

	/**
	 * 注册属性bean
	 *
	 * @param registry bean注册器
	 * @return 属性bean
	 */
	private HessianClientProperties registerHessianClientProperties(BeanDefinitionRegistry registry) {
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(HessianClientProperties.class);

		String propertyPrefix = getPrefix();
		HessianClientProperties hessianClientProperties = new HessianClientProperties();
		Field[] declaredFields = hessianClientProperties.getClass().getDeclaredFields();
		for (Field field : declaredFields) {
			if (!TypeUtils.isStatic(field.getModifiers())) {
				String fieldName = field.getName();
				String placeHolder = propertyPrefix + "." + fieldName;
				String value = this.environment.getProperty(placeHolder);
				if (value != null) {
					Class<?> fieldType = field.getType();
					Object propertyValue = convertValue(fieldType, value);
					beanDefinition.getPropertyValues().add(fieldName, propertyValue);

					ReflectionUtils.makeAccessible(field);
					ReflectionUtils.setField(field, hessianClientProperties, propertyValue);
				}
			}
		}
		registry.registerBeanDefinition(HessianClientProperties.class.getSimpleName(), beanDefinition);
		return hessianClientProperties;
	}

	/**
	 * 创建bean并且放入springBean工厂，取代xml的繁琐配置
	 *
	 * @param registry
	 * @param beanDefinition
	 * @param hessianClientProperties
	 */
	private void registerHessianClient(BeanDefinitionRegistry registry, BeanDefinition beanDefinition, HessianClientProperties hessianClientProperties) {
		try {
			String className = beanDefinition.getBeanClassName();
			Class<?> serviceInterface = Class.forName(className);
			String simpleName = serviceInterface.getSimpleName();

			String serverUrl = hessianClientProperties.getServerUrl();
			String prefix = hessianClientProperties.getPrefix();
			int connTimeout = hessianClientProperties.getConnTimeout();
			int readTimeout = hessianClientProperties.getReadTimeout();

			String serviceUrl;
			if (!StringUtils.endsWithIgnoreCase(serverUrl, "/")) {
				serverUrl += "/";
			}
			if (StringUtils.hasText(prefix)) {
				serverUrl += prefix;
			}
			serviceUrl = serverUrl + simpleName + hessianClientProperties.getSuffix();

			BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(HessianProxyFactoryBean.class);

			definition.addPropertyValue("serviceUrl", serviceUrl);
			definition.addPropertyValue("serviceInterface", serviceInterface);
			definition.addPropertyValue("overloadEnabled", hessianClientProperties.isOverloadEnabled());
			definition.addPropertyValue("connectTimeout", connTimeout * 1000);
			definition.addPropertyValue("readTimeout", readTimeout * 1000);
			definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

			AbstractBeanDefinition abstractBeanDefinition = definition.getBeanDefinition();
			BeanDefinitionHolder holder = new BeanDefinitionHolder(abstractBeanDefinition, StringUtils.uncapitalize(simpleName));
			BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

			log.info("Register HessianClientBean=>\n {} || {} || {} || {}", serviceUrl, serviceInterface, connTimeout, readTimeout);
		} catch (ClassNotFoundException e) {
			//ignore this
		}
	}

	/**
	 * 获取带扫描的包路径
	 *
	 * @param importingClassMetadata 注解的属性
	 * @return 包路径集合
	 */
	protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
		Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableHessianClient.class.getCanonicalName());
		Set<String> basePackages = new HashSet<>();
		for (String pkg : (String[]) attributes.get("value")) {
			if (StringUtils.hasText(pkg)) {
				basePackages.add(pkg);
			}
		}
		for (String pkg : (String[]) attributes.get("basePackages")) {
			if (StringUtils.hasText(pkg)) {
				basePackages.add(pkg);
			}
		}
		for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
		}
		return basePackages;
	}

	/**
	 * 获取扫描器
	 *
	 * @return 扫描器
	 */
	protected ClassPathScanningCandidateComponentProvider getScanner() {

		return new ClassPathScanningCandidateComponentProvider(false, this.environment) {

			@Override
			protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
				if (beanDefinition.getMetadata().isIndependent()) {
					if (beanDefinition.getMetadata().isInterface()
							&& beanDefinition.getMetadata().getInterfaceNames().length == 1
							&& Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
						try {
							Class<?> target = ClassUtils.forName(
									beanDefinition.getMetadata().getClassName(),
									HessianClientRegistrar.this.classLoader);
							return !target.isAnnotation();
						} catch (Exception ex) {
							this.logger.error(
									"Could not load target class: "
											+ beanDefinition.getMetadata().getClassName(),
									ex);

						}
					}
					return true;
				}
				return false;
			}
		};
	}

	/**
	 * 属性值读取，暂未找到合适的工具类
	 *
	 * @param fieldType 属性类型
	 * @param value     读取的string值
	 * @return 属性值
	 */
	private Object convertValue(Class<?> fieldType, String value) {
		if (fieldType.isAssignableFrom(String.class)) {
			return value;
		} else if (fieldType.isAssignableFrom(int.class)) {
			return Integer.parseInt(value);
		} else if (fieldType.isAssignableFrom(boolean.class)) {
			return Boolean.parseBoolean(value);
		} else {
			return null;
		}
	}

	/**
	 * 获取配置的前缀，value 优先级高
	 *
	 * @return 配置前缀
	 */
	private String getPrefix() {
		ConfigurationProperties configurationProperties = HessianClientProperties.class.getAnnotation(ConfigurationProperties.class);
		String value = configurationProperties.value();
		if (!StringUtils.hasText(value)) {
			value = configurationProperties.prefix();
		}
		return value;
	}
}
